/*
 * midi.c
 *
 * 		MIDI handling functions.
 *
 */

#include <midi.h>

// FIFO buffer for received MIDI messages
FIFO_BUFFER 		midi_buffer;
volatile uint8_t	midi_data_store[FIFO_BUFFER_SIZE];

// array for MIDI frequencies
static float noteFrequencies[128];

/****************************************************************************
* DESCRIPTION: Initialise the UART for use with MIDI
* RETURN:      none
*****************************************************************************/
void midi_init(void) {

	PINSEL_CFG_Type PinCfg;

	/*
	* Initialize UART1 pin connect
	*/
	PinCfg.Funcnum = PINSEL_FUNC_1;
	PinCfg.OpenDrain = PINSEL_PINMODE_NORMAL;
	PinCfg.Pinmode = PINSEL_PINMODE_PULLDOWN;
	PinCfg.Portnum = PINSEL_PORT_0;
	PinCfg.Pinnum = PINSEL_PIN_16;
	PINSEL_ConfigPin(&PinCfg);

	// UART Configuration structure variable
	UART_CFG_Type UARTConfigStruct;

	// UART Configuration
	UARTConfigStruct.Baud_rate = 31250;
	UARTConfigStruct.Databits = UART_DATABIT_8;
	UARTConfigStruct.Parity = UART_PARITY_NONE;
	UARTConfigStruct.Stopbits = UART_STOPBIT_1;

	// UART init
	UART_Init( MIDI_UART, &UARTConfigStruct );

	// Disable UART Transmit
	UART_TxCmd( MIDI_UART, DISABLE);

	// Enable interrupts
	UART_IntConfig(MIDI_UART, UART_INTCFG_RBR, ENABLE);
	UART_IntConfig(MIDI_UART, UART_INTCFG_RLS, ENABLE);

	// Enable interrupts
	NVIC_EnableIRQ(UART1_IRQn);

	// init FIFO queue
	FIFO_Init(&midi_buffer, midi_data_store, sizeof(midi_data_store));

	// init array for MIDI note number to frequency look-up
	midi_notes_init();
}

/****************************************************************************
* DESCRIPTION: Calculate frequencies for MIDI notes
* RETURN:      none
*****************************************************************************/
void midi_notes_init() {
	int32_t i;
	float p;

	// calculate frequencies
	for(i = 0;i < 128; i++) {
		p = (i-69)/12.0;
		noteFrequencies[i] = 440 * pow(2, p );
	}
}

/****************************************************************************
* DESCRIPTION: Check received MIDI messages for complete message
* RETURN:      none
*****************************************************************************/
void midi_check() {

	uint8_t message = 0;				// temp message store
	bool completeMessage = false;		// complete message?
	static uint8_t runningStatus = 0;	// remember running status
	static uint8_t localMessage[3];		// remember partial message
	static uint8_t bytesReceived = 0;	// remember bytes received

	// get status byte/first message
	if( FIFO_Empty(&midi_buffer) == false ) {
		message = FIFO_Peek(&midi_buffer);
	}

	/* decide message type, 1 for status message
	 * or 0 for data message */
	uint8_t messageType = message >> 7;

	/* if new message
	 * message is status byte so expect 3 bytes */
	if( messageType == 1 ) {
		// remember for running status
		runningStatus = message;

		// get next byte if available
		if( FIFO_Empty(&midi_buffer) == false ) {
			localMessage[0] = FIFO_Get(&midi_buffer);
			bytesReceived = 1;

			// get next byte if available
			if( FIFO_Empty(&midi_buffer) == false ) {
				localMessage[1] = FIFO_Get(&midi_buffer);
				bytesReceived = 2;

				// get next byte is available
				if( FIFO_Empty(&midi_buffer) == false ) {
					localMessage[2] = FIFO_Get(&midi_buffer);
					bytesReceived = 0;	// all bytes received so reset
					completeMessage = true;
				}
			}
		}
	}
	// else message is incomplete or uses running status
	else {
		/* if last message was complete then this message
		 * makes use of running status, receive 2 bytes */
		if( bytesReceived == 0 ) {
			// status byte
			localMessage[0] = runningStatus;
			bytesReceived = 1;

			// get next byte if available
			if( FIFO_Empty(&midi_buffer) == false ) {
				localMessage[1] = FIFO_Get(&midi_buffer);
				bytesReceived = 2;

				// get next byte is available
				if( FIFO_Empty(&midi_buffer) == false ) {
					localMessage[2] = FIFO_Get(&midi_buffer);
					bytesReceived = 0;	// all bytes received so reset
					completeMessage = true;
				}
			}
		}
		// else this in an incomplete message
		else {
			int32_t i;

			// status byte
			localMessage[0] = runningStatus;

			// receive rest of message
			for( i = bytesReceived; i != 3; i++) {

				// get next byte if available
				if( FIFO_Empty(&midi_buffer) == false ) {
					localMessage[bytesReceived] = FIFO_Get(&midi_buffer);
					bytesReceived++;

					// if 3 bytes received then message complete
					if( bytesReceived == 3) {
						completeMessage = true;
						bytesReceived = 0;
					}
				}
			}
		}
	}

	// if complete message received pass message to midi parse
	if(completeMessage == true) {
		midi_parse(&localMessage[0]);
	}
}

/****************************************************************************
* DESCRIPTION: Parse MIDI message into correct message type
* RETURN:      none
*****************************************************************************/
void midi_parse(uint8_t* message) {

	uint8_t noteID		 = 0, noteVelocity 	  = 0,
			controllerID = 0, controllerValue = 0;

	// if message is a note on or off message
	if(message[0] == 0x90 || message[0] == 0x80) {
		// get note
		noteID = message[1];

		// get velocity
		noteVelocity = message[2];

		// turn note on and velocity
		if(noteVelocity != 0) {
			midi_note_on(noteID, noteVelocity);
		}
		else {
			// turn note off
			midi_note_off(noteID);
		}
	}
	// if message is controller change message
	else if(message[0] == 0xb0) {
		// get controller ID
		controllerID = message[1];

		// get controller value
		controllerValue = message[2];

		// update controller value
		if (controllerID == 0x01) {
			// select waveform
			voice_select_waveform(controllerValue);
		}
		else if (controllerID == 0x47) {
			// amplitude envelope attack time
			voice_amp_env_attack(controllerValue);
		}
		else if (controllerID == 0x51) {
			// amplitude envelope decay time
			voice_amp_env_decay(controllerValue);
		}
		else if (controllerID == 0x5b) {
			// amplitude envelope release time
			voice_amp_env_release(controllerValue);
		}
		else if (controllerID == 0x10) {
			// filter resonance
			voice_filter_resonance(controllerValue);
		}
		else if (controllerID == 0x50) {
			// filter envelope attack time
			voice_filter_env_attack(controllerValue);
		}
		else if (controllerID == 0x13) {
			// filter envelope decay time
			voice_filter_env_decay(controllerValue);
		}
		else if (controllerID == 0x02) {
			// filter envelope release time
			voice_filter_env_release(controllerValue);
		}
	}
}

/****************************************************************************
* DESCRIPTION: Handle MIDI note on message
* RETURN:      none
*****************************************************************************/
void midi_note_on(uint8_t noteID, uint8_t velocity) {

	// create a new note to add to linked-list
	note_s newNote;
	// fill in note values
	newNote.noteID = noteID;
	newNote.frequency = noteFrequencies[noteID];
	newNote.velocity = velocity * 0.0039370078740157;	// 0 - 0.5

	// check if note is in linked-list already
	if(linkedlist_noteAlreadyInList(noteID) == false)	// if not then
		linkedlist_addBeg(newNote);						// add note to list

	/* if voice is not sounding trigger new note
	 * else trigger a continuation of the currently sounding voice.
	 * avoids the re-triggering of amplitude envelope on every new note */
	if(voice_state() == noteOff || voice_state() == voiceOff)
		voice_note_on(newNote.frequency, newNote.velocity);
	else
		voice_note_on_continue(newNote.frequency, newNote.velocity);
}

/****************************************************************************
* DESCRIPTION: Handle MIDI note off message
* RETURN:      none
*****************************************************************************/
void midi_note_off(uint8_t noteID) {

	// delete note from linked-list
	linkedlist_delNodeData(noteID);

	// if no notes left in linked list
	if(linkedlist_empty() == true)
		// trigger note off message to voice
		voice_note_off();
	else {
		// else get note at start of linked-list
		note_s newNote = linkedlist_getBeg();

		/* if voice is not sounding trigger new note
		 * else trigger a continuation of the currently sounding voice.
		 * avoids the re-triggering of amplitude envelope on every new note */
		if(voice_state() == noteOff || voice_state() == voiceOff)
			voice_note_on(newNote.frequency, newNote.velocity);
		else
			voice_note_on_continue(newNote.frequency, newNote.velocity);
	}
}
